/*
 ============================================================================
 Name        : TP0.c
 Author      : Forlenza-Ricci
 Version     : 1.0
 Copyright   :
 Description :
 ============================================================================
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "TP0.h"

tCoordinate complex2Pow(tCoordinate coordinate) {
	float a = pow(coordinate.x, 2.0) - pow(coordinate.y, 2.0);
	float b = 2.0 * coordinate.x * coordinate.y;
	tCoordinate result;
	result.x = a;
	result.y = b;
	return result;
}

tCoordinate complexSum(tCoordinate z1, tCoordinate z2) {
	tCoordinate result;
	result.x = z1.x + z2.x;
	result.y = z1.y + z2.y;
	return result;
}

float module(tCoordinate coordinate) {
	return sqrt(pow(coordinate.x, 2.0) + pow(coordinate.y, 2.0));
}

float applyFunction(tCoordinate coordinate) {
	int end = 0;
	int i = 0;
	tCoordinate result;
	result.x = coordinate.x;
	result.y = coordinate.y;

	while (!end && i < MAX_ITERATIONS) {
		if(i != 0)
		{
			result = complexSum(complex2Pow(result), coordinate);
		}
		float moduleAux = module(result);
 		if (moduleAux > COMPARE_LIMIT) {
			end = 1;
		} else {
			i++;
		}
	}
	return i;
}

tCoordinate createCoordinate(float x, float y) {
	tCoordinate result;
	result.x = x;
	result.y = y;
	return result;
}

void createHeader(float width, float height, int resolutionWidth, int resolutionHeight, tCoordinate center, int fileOutput, char* fileName, tPGMHeader* result) {
	result->resolutionHeight = resolutionHeight;
	result->resolutionWidth = resolutionWidth;
	//Check if height is a line
	if(resolutionHeight!=1){
		result->heght = height;
	}else{
		result->heght = 0.0;
	}
	//Check if width is a line
	if(resolutionWidth!=1){
		result->width = width;
	}else{
		result->width = 0.0;
	}
	result->maxGreyValue = MAX_GREY_VALUE;
	result->magicNumber = MAGIC_NUMBER;
	result->center = center;
	result->fileOutput = fileOutput;
	result->outputName = fileName;
}

/*
 * No se chequean errores de printf ya que las salidas están asociadas a un error más importante y no se desea cambiar el código de error
 */
tPGMHeader* createHeaderFromParams(int paramCount,char** params){
	tPGMHeader* result = (tPGMHeader*) malloc(sizeof(tPGMHeader));
	if(result == NULL){
		printf("Error al reservar memoria");
		exit(-1);
	}
	int i = 0;
	int centerParameterFound=0;
	int resolutionParameterFound=0;
	int widthParameterFound=0;
	int heightParameterFound=0;
	int outputParameterFound=0;
	int fileNameFound=0;
	char fileName[255];
	strcpy(fileName,"");
	float centerX = 0.0;
	float centerY = 0.0;
	int resolutionWidth = DEFAULT_RESOLUTION_W;
	int resolutionHeight = DEFAULT_RESOLUTION_H;
	float width = DEFAULT_WIDTH;
	float height = DEFAULT_HEGHT;
	for(i=0;i<paramCount;i++){
		// Center specification
		if(strcmp(params[i],"-c")==0 || strcmp(params[i],"--center")==0 ){
			if (centerParameterFound) {
				result->errorFound = 1;
				printf("fatal: duplicate center specification");
				return result;
			}
			if(i+1>=paramCount){
				result->errorFound = 1;
				printf("fatal: invalid center specification");
				return result;
			}
			centerParameterFound = 1;
			int nextSize=strlen(params[i+1]);
			if (params[i+1][nextSize-1]!='i') {
				result->errorFound = 1;
				printf("fatal: invalid center specification");
				return result;
			}
			char* complexCenter = params[i+1];
			char realComponent[255];
			char complexComponent[255];
			int complexIndex=0;
			int j=0;
			while(j<nextSize-1){
				if((complexCenter[j]>='0' && complexCenter[j]<='9')||(complexCenter[j]=='.')||(j==0 && complexCenter[j]=='+')||(j==0 && complexCenter[j]=='-')){
					if(!complexIndex){
						realComponent[j]=complexCenter[j];
					}else{
						complexComponent[j-complexIndex]=complexCenter[j];
					}
				}else if(complexCenter[j]=='+' || complexCenter[j]=='-'){
					complexIndex = j;
					realComponent[j]='\0';
					complexComponent[0]=complexCenter[j];
				}else{
					if (params[i + 1][nextSize] != 'i') {
						result->errorFound = 1;
						printf(
								"fatal: invalid center specification");
						return result;
					}
				}
				j++;
			}
			complexComponent[j-complexIndex]='\0';
			centerX=atof(realComponent);
			centerY=atof(complexComponent);
		}
		// Resolution specification
		if(strcmp(params[i],"-r")==0 || strcmp(params[i],"--resolution")==0 ){
			if (resolutionParameterFound) {
				result->errorFound = 2;
				printf("fatal: duplicate resolution specification");
				return result;
			}
			if (i + 1 >= paramCount) {
				result->errorFound = 2;
				printf("fatal: invalid resolution specification");
				return result;
			}
			int nextSize=strlen(params[i+1]);
			char* resolution = params[i+1];
			char resW[255];
			char resH[255];
			int j=0;
			while(j<nextSize){
				if((resolution[j]>='0' && resolution[j]<='9')){
					if(!resolutionParameterFound){
						resW[j]=resolution[j];
					}else{
						resH[j-resolutionParameterFound-1]=resolution[j];
					}
				}else if(resolution[j]=='x'){
					resW[j]='\0';
					resolutionParameterFound=j;
				}
				j++;
			}
			resH[j-resolutionParameterFound-1]='\0';
			if (!resolutionParameterFound) {
				result->errorFound = 2;
				printf("fatal: invalid resolution specification");
				return result;
			}
			resolutionWidth = atoi(resW);
			resolutionHeight = atoi(resH);
		}
		// Width specification
		if(strcmp(params[i],"-w")==0 || strcmp(params[i],"--width")==0 ){
			if (widthParameterFound) {
				result->errorFound = 3;
				printf("fatal: duplicate width specification");
				return result;
			}
			if (i + 1 >= paramCount) {
				result->errorFound = 3;
				printf("fatal: invalid width specification");
				return result;
			}
			widthParameterFound = 1;
			int nextSize = strlen(params[i + 1]);
			char* widthParam = params[i+1];
			int j = 0;
			while (j < nextSize) {
				if ((widthParam[j] < '0' || widthParam[j] > '9') && (widthParam[j]!='.')) {
					result->errorFound = 3;
					printf("fatal: invalid width specification");
					return result;
				}
				j++;
			}
			width=atof(widthParam);
		}
		// Width specification
		if (strcmp(params[i], "-H") == 0 || strcmp(params[i], "--height") == 0) {
			if (heightParameterFound) {
				result->errorFound = 4;
				printf("fatal: duplicate height specification");
				return result;
			}
			if (i + 1 >= paramCount) {
				result->errorFound = 4;
				printf("fatal: invalid height specification");
				return result;
			}
			heightParameterFound = 1;
			int nextSize = strlen(params[i + 1]);
			char* heightParam = params[i + 1];
			int j = 0;
			while (j < nextSize) {
				if ((heightParam[j] < '0' || heightParam[j] > '9')
						&& (heightParam[j] != '.')) {
					result->errorFound = 4;
					printf("fatal: invalid height specification");
					return result;
				}
				j++;
			}
			height = atof(heightParam);
		}
		// Output specification
		if (strcmp(params[i], "-o") == 0 || strcmp(params[i], "--output") == 0) {
			if (outputParameterFound) {
				result->errorFound = 5;
				printf("fatal: duplicate output specification");
				return result;
			}
			if (i + 1 >= paramCount) {
				result->errorFound = 5;
				printf("fatal: invalid output specification");
				return result;
			}
			outputParameterFound = 1;
			char* outputParam = params[i + 1];
			if(strcmp(outputParam,"-")==0){
				fileNameFound=0;
			}else{
				fileNameFound=1;
				strcpy(fileName,outputParam);
			}
		}
	}
	if (!outputParameterFound) {
		result->errorFound = 5;
		printf("fatal: no output");
		return result;
	}
	if(resolutionWidth<=0 || resolutionHeight<=0){
		result->errorFound = 6;
		printf("Invalid resolution");
		return result;
	}
	createHeader(width,height,resolutionWidth,resolutionHeight,createCoordinate(centerX,centerY),fileNameFound,fileName,result);
	return result;
}

int writeHeaderToFile(FILE* file, tPGMHeader* header) {
	int result = fprintf(file, "%s\n%i %i\n%i\n", header->magicNumber, header->resolutionWidth,
		header->resolutionHeight, header->maxGreyValue);
	if(result<0){
		return -1;
	}
	return 0;
}

int writeHeaderToConsole(tPGMHeader* header) {
	int result = printf("%s\n%i %i\n%i\n", header->magicNumber, header->resolutionWidth,
		header->resolutionHeight, header->maxGreyValue);
	if(result<0){
		return -1;
	}
	return 0;
}

int writeDetailToFile(FILE* file, tPGMHeader* header) {
	int width = header->resolutionWidth;
	int widthAux = width;
	if(width == 1){
		widthAux = 2;
	}
	int height = header->resolutionHeight;
	int heightAux = height;
	if(height == 1){
		heightAux = 2;
	}
	float WIDTH = header->width;
	float HEIGHT = header->heght;
	int i, j;
	for (i = 0; i < height; i++) {
		for (j = 0; j < width; j++) {
			char aux[12];
			int intensity = applyFunction(complexSum(createCoordinate(-1*WIDTH/2.0+ j* WIDTH*1.0/(widthAux-1.0) , HEIGHT/2.0 - i * HEIGHT*1.0/(heightAux-1.0)),header->center));
			if (j != width - 1) {
				if(sprintf(aux, "%i ", intensity)<0){
					return -1;
				}
			} else {
				if(sprintf(aux, "%i", intensity)<0){
					return -1;
				}
			}
			int writeResult = fputs(aux, file);
			if(writeResult<0 || writeResult==EOF){
				return -1;
			}
		}
		if (i != height - 1) {
			if (fputc('\n', file)==EOF){
				return -1;
			}
		}
	}
	return 0;
}

int writeDetailToConsole(tPGMHeader* header) {
	int width = header->resolutionWidth;
	int widthAux = width;
	if(width == 1){
		widthAux = 2;
	}
	int height = header->resolutionHeight;
	int heightAux = height;
	if(height == 1){
		heightAux = 2;
	}
	float WIDTH = header->width;
	float HEIGHT = header->heght;
	int i, j;
	for (i = 0; i < height; i++) {
		for (j = 0; j < width; j++) {
			char aux[12];
			int intensity = applyFunction(complexSum(createCoordinate(-1*WIDTH/2.0+ j* WIDTH*1.0/(widthAux-1.0) , HEIGHT/2.0 - i * HEIGHT*1.0/(heightAux-1.0)),header->center));
			if (j != width - 1) {
				if(sprintf(aux, "%i ", intensity)<0){
					return -1;
				}
			} else {
				if(sprintf(aux, "%i", intensity)<0){
					return -1;
				}
			}
			if(printf("%s",aux)<0){
				return -1;
			}
		}
		if (i != height - 1) {
			if(printf("\n")<0){
				return -1;
			}
		}
	}
	return 0;
}
int main(int argc, char **argv) {

	tPGMHeader* header = createHeaderFromParams(argc,argv);
	int fileOutput = header->fileOutput;
	if(header->errorFound !=0){
		free(header);
		return header->errorFound;
	}

	/*
	 * No se chequean errores de printf ya que las salidas están asociadas a un error más importante y no se desea cambiar el código de error
	 */
	if(fileOutput){
		FILE* filePtr;
		filePtr = fopen(header->outputName, "w");
		if (!filePtr) {
			printf("fatal: cannot open output file.");
			free(header);
			return 7;
		}

		int headerResult = writeHeaderToFile(filePtr, header);
		if (headerResult != 0) {
			printf("fatal: error writing header.");
			free(header);
			fclose(filePtr);
			return 8;
		}
		int detailResult = writeDetailToFile(filePtr, header );
		if (detailResult != 0) {
			printf("fatal: error writing content.");
			free(header);
			fclose(filePtr);
			return 9;
		}
		fclose(filePtr);
	}else{
		int headerResult = writeHeaderToConsole(header);
		if(headerResult!=0){
			printf("fatal: error writing header.");
			free(header);
			return 8;
		}
		int detailResult = writeDetailToConsole(header);
		if (detailResult != 0) {
			printf("fatal: error writing content.");
			free(header);
			return 9;
		}

	}

	free(header);

	return EXIT_SUCCESS;
}
